/**
 * 
 */
package info.niwota.webapp.web;

import info.niwota.commons.Debug;
import info.niwota.commons.MimeTypes;
import info.niwota.commons.web.PageCursor;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;

/**
 * @author qiangli
 * 
 */
public abstract class BasePageContentProvider extends ContentProvider implements
		MimeTypes, PageCursor.Type {

	private static class TempParcelFileDescriptor extends ParcelFileDescriptor {

		private File temp = null;

		public TempParcelFileDescriptor(ParcelFileDescriptor fd, File temp) {
			super(fd);
			this.temp = temp;
		}

		@Override
		public void close() throws IOException {
			if (Debug.DEBUG) {
				Log.d(TAG, "close: " + temp);
			}
			try {
				super.close();
				temp.delete();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private static final String TAG = "BasePageContentProvider";

	private static long seq = System.currentTimeMillis();

	private static final int BUFSIZE = 1024;

	private static String tmpprefix = BasePageContentProvider.class
			.getSimpleName();

	public static void closeSilently(Closeable c) {
		if (c == null) {
			return;
		}
		try {
			c.close();
		} catch (Throwable t) {
			// do nothing
		}
	}

	protected static File createTempFile(File cacheDir) throws IOException {
		File f = new File(cacheDir, nextTempName());
		f.deleteOnExit();
		return f;
	}

	protected static ParcelFileDescriptor createTempParcelFileDescriptor(
			File temp) throws FileNotFoundException {
		ParcelFileDescriptor fd = ParcelFileDescriptor.open(temp,
				ParcelFileDescriptor.MODE_READ_ONLY);
		return new TempParcelFileDescriptor(fd, temp);
	}

	synchronized private static String nextTempName() {
		seq++;
		return tmpprefix + seq + ".tmp";
	}

	protected File cacheDir = null;

	@Override
	public void attachInfo(Context context, ProviderInfo info) {
		super.attachInfo(context, info);
	}

	@Override
	public int bulkInsert(Uri uri, ContentValues[] values) {
		return 0;
	}

	/**
	 * Save resource under assets/ to a file
	 * 
	 * @param name
	 *            relative path in assets e.g image/bg.png
	 * @param file
	 * @throws IOException
	 */
	protected void copyAsset(String name, File file) throws Exception {
		Context ctx = getContext();
		AssetManager am = ctx.getAssets();
		InputStream is = null;
		OutputStream os = null;
		try {
			is = am.open(name);
			os = new FileOutputStream(file);
			copy(is, os);
		} catch (Exception e) {
			file.delete();
			throw e;
		} finally {
			closeSilently(is);
			closeSilently(os);
		}
	}

	/**
	 * Save resource under res/ to a file
	 * 
	 * @param resid
	 *            int generated in R.java
	 * @param file
	 * @throws IOException
	 */
	protected void copyRes(int resid, File file) throws Exception {
		if (Debug.DEBUG) {
			Log.d(TAG, "copyDrawableRes resId: " + resid + " file: " + file);
		}
		Context ctx = getContext();
		InputStream is = null;
		OutputStream os = null;
		try {
			is = ctx.getResources().openRawResource(resid);
			os = new FileOutputStream(file);
			copy(is, os);
		} catch (Exception e) {
			file.delete();
			throw e;
		} finally {
			closeSilently(is);
			closeSilently(os);
		}
	}

	protected boolean save(Drawable image, OutputStream fos) throws IOException {
		final CompressFormat cf = CompressFormat.JPEG;
		final int alpha = 100;
		Bitmap bitmap = null;
		if (image instanceof BitmapDrawable) {
			bitmap = ((BitmapDrawable) image).getBitmap();
		} else {
			final Config bc = Bitmap.Config.ARGB_8888;
			final int w = image.getIntrinsicWidth();
			final int h = image.getIntrinsicHeight();
			bitmap = Bitmap.createBitmap(w, h, bc);
			Canvas c = new Canvas(bitmap);
			c.drawColor(Color.WHITE);
			image.setBounds(0, 0, w, h);
			image.setAlpha(alpha);
			image.setDither(true);
			image.draw(c);
		}
		//
		final int q = 100;
		return bitmap.compress(cf, q, fos);
	}

	/**
	 * Save resource in the package to a file
	 * 
	 * @param resName
	 *            dots replaced with slash e.g.
	 *            /info/niwota/webapp/Constants.class
	 * @param file
	 * @throws IOException
	 */
	protected void copyClassResource(String resName, File file)
			throws Exception {
		InputStream is = null;
		OutputStream os = null;
		try {
			is = getClass().getResourceAsStream(resName);
			os = new FileOutputStream(file);
			copy(is, os);
		} catch (Exception e) {
			file.delete();
			throw e;
		} finally {
			closeSilently(is);
			closeSilently(os);
		}
	}

	protected void copy(InputStream is, Appendable out) throws IOException {
		BufferedReader r = new BufferedReader(new InputStreamReader(is),
				BUFSIZE);
		while (true) {
			String l = r.readLine();
			if (l == null) {
				break;
			}
			out.append(l);
			out.append("\r\n");
		}
	}

	protected void copy(InputStream is, OutputStream os) throws IOException {
		byte[] buf = new byte[BUFSIZE];
		while (true) {
			int read = is.read(buf);
			if (read == -1) {
				break;
			}
			if (read > 0) {
				os.write(buf, 0, read);
			}
		}
		os.flush();
	}

	protected ParcelFileDescriptor createParcelFileDescriptor(File file)
			throws Exception {
		final int mode = ParcelFileDescriptor.MODE_READ_ONLY;
		ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, mode);
		return fd;
	}

	protected ParcelFileDescriptor createParcelFileDescriptor(InputStream data)
			throws IOException {
		File f = createTempFile(cacheDir);
		OutputStream os = new FileOutputStream(f);
		BufferedOutputStream bos = new BufferedOutputStream(os, BUFSIZE);
		copy(data, bos);
		data.close();
		bos.close();
		return createTempParcelFileDescriptor(f);
	}

	protected ParcelFileDescriptor createParcelTempFileDescriptor(String data)
			throws Exception {
		File f = createTempFile(cacheDir);
		FileWriter fw = new FileWriter(f);
		BufferedWriter bw = new BufferedWriter(fw);
		bw.write(data);
		bw.close();
		return createTempParcelFileDescriptor(f);
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		throw new UnsupportedOperationException();
	}

	@Override
	public String getType(Uri uri) {
		String type = uri.getQueryParameter("type");
		if (type != null) {
			return type;
		}
		return null;
	}

	/**
	 * Handle file
	 * 
	 * @param uri
	 * @param mode
	 * @return
	 * @throws Exception
	 */
	protected abstract ParcelFileDescriptor handle(Uri uri, String mode)
			throws Exception;

	/**
	 * Handle query
	 * 
	 * @param uri
	 * @param projection
	 * @param selection
	 * @param selectionArgs
	 * @param sortOrder
	 * @return
	 * @throws Exception
	 */
	protected abstract PageCursor handle(Uri uri, String[] projection,
			String selection, String[] selectionArgs, String sortOrder)
			throws Exception;

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		throw new UnsupportedOperationException();
	}

	@Override
	protected boolean isTemporary() {
		return false;
	}

	@Override
	public boolean onCreate() {
		try {
			Context ctx = getContext();
			cacheDir = new File(ctx.getCacheDir(), this.getClass().getName());
			cacheDir.mkdirs();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}

	@Override
	public void onLowMemory() {
		super.onLowMemory();
		//
		// clear cache
	}

	@Override
	public AssetFileDescriptor openAssetFile(Uri uri, String mode)
			throws FileNotFoundException {
		ParcelFileDescriptor fd = openFile(uri, mode);
		return (fd != null ? new AssetFileDescriptor(fd, 0, -1) : null);
	}

	@Override
	public ParcelFileDescriptor openFile(Uri uri, String mode)
			throws FileNotFoundException {
		if (Debug.DEBUG) {
			Log.d(TAG, "***** open: " + uri + " mode: " + mode);
		}
		try {
			ParcelFileDescriptor pfd = handle(uri, mode);
			return pfd;
		} catch (Exception e) {
			e.printStackTrace();
		}
		throw new FileNotFoundException(uri + "");
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		if (Debug.DEBUG) {
			Log.d(TAG, "*** query: "
					+ uri
					+ " projection: "
					+ (projection == null ? "" : Arrays.asList(projection))
					+ " selection: "
					+ selection
					+ " args: "
					+ (selectionArgs == null ? "" : Arrays
							.asList(selectionArgs)) + " sortOrder: "
					+ sortOrder);
		}

		try {
			PageCursor c = handle(uri, projection, selection, selectionArgs,
					sortOrder);
			c.setNotificationUri(getContext().getContentResolver(), uri);

			if (Debug.DEBUG) {
				Log.d(TAG, "*** query: cursor: " + c);
			}
			return c;
		} catch (Throwable e) {
			e.printStackTrace();
			//
			return handleError(e);
		}
	}

	protected PageCursor handleError(Throwable e) {
		PageCursor c = new PageCursor();
		showError(c, e);
		return c;
	}
	
	protected boolean showError(PageCursor c, Throwable e) {
		c.type = PageCursor.Type.ERROR;
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		pw.print(e.getLocalizedMessage());
		
		if (Debug.DEBUG) {
			e.printStackTrace();
		}
		
		String s = sw.toString();
		c.content = s;
		c.contentLength = s.length();
		c.contentType = TEXT_PLAIN;
		
		return true;
	}

	protected String scan(String cookies, String key) {
		if (TextUtils.isEmpty(cookies)) {
			return null;
		}
		final String[] sa = cookies.split(";");
		final String key_ = key + "=";
		for (int i = 0; i < sa.length; i++) {
			String sai = sa[i].trim();
			if (sai.startsWith(key_)) {
				return sai.substring(key.length() + 1);
			}
		}
		return null;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		throw new UnsupportedOperationException();
	}

	protected Object NVL(Object value, Object replace) {
		return (value == null ? replace : value);
	}
}
